//
// Created by LiuYou on 2021/10/21.
//

#include <iostream>
#include <unistd.h>
#include <csignal>


static void signalHandler(int sig) {
    ssize_t writeBytes;
    do {
        writeBytes = write( 1, "!", 1 );
        if (writeBytes < 0) {
            if (errno == EINTR) {
                continue;
            }
            perror( "write()" );
        }
    } while (writeBytes < 0);
}


/*
 * 连续按 ctrl + c 的话, 程序会立马结束。
 * 原因: 信号会打断阻塞的系统调用。
 *
 * 是的, 信号会打断阻塞的系统调用。所以！！！！！
 * 之前写的所有的程序没有一个是正确的, 因为都没有判断如果有信号来打断一个程序中某个阻塞的系统调用
 * 要怎么办?
 * 例如: 如果一个程序使用了 open(); 系统调用函数, man 2 open 看 RETURN VALUE
 * 的下一项: ERRORS 中 有一项是 EINTR , 这个宏是让非常多的程序员为之崩溃的一个宏名。

EINTR  While blocked waiting to complete an open of a slow device (e.g., a FIFO; see fifo(7)),
the call was interrupted by a signal handler; see signal(7).
 当你阻塞着等待打开一个慢的设备的时候, 那么这个调用有可能被一个信号打断。
 当一个阻塞的系统调用函数在执行时, 就可能会被信号打断。

再例如: man 2 read
EINTR  The call was interrupted by a signal before any data was read; see signal(7).
 在读取任何数据之前调用被信号中断；见信号(7)。
 比如: 读, 在读一个设备, 读一个打印机, 打印机现在没有内容, 那就阻塞读, 一直在读, 还是没有数据,
 然后这个时候来了一个信号, 这个读会被打断, 然后 read(); 返回错误, 但是返回的并不是真错。
 是假错, 所以加上信号机制, 我之前写的程序没有一个是正确的。

 所以改进是: 把一个程序中使用到的阻塞的系统调用在发生出错现象的时候, 都要判断一下到底是真错还是假错!!!!
 例如:
 sFd = open(argv[1], O_RDONLY);
 if(sFd < 0) {
    perror("open()");
    exit(1);
 }
 改为:
 do {
    sFd = open(argv[1], O_RDONLY);
    if(sFd < 0) {
    if(errno != EINTR) {
        perror("open()");
        exit(1);
        }
    }
 } while(sFd < 0);

 再如:
 readBytes = read(sFd, buf, BUF_SIZE);
 if(readBytes < 0) {
    perror("read()");
    exit(1);
 }
 改为:
 readBytes = read(sFd, buf, BUF_SIZE);
 if(readBytes < 0) {
    if(errno == EINTR) {
        continue;
    }
    perror("read()");
    exit(1);
 }

 再如:
 write();也是有假错的, 因为 write(); 也是阻塞的。
 writeBytes = write(dFd, buf, BUF_SIZE);
 if(writeBytes < 0) {
    perror("write()");
    exit(1);
 }
 改为:
 writeBytes = write(dFd, buf, BUF_SIZE);
 if(writeBytes < 0) {
    if(errno == EINTR) {
        continue;
    }
    perror("write()");
    exit(1);
 }

 * 综上, 如果一个程序中使用了阻塞的系统调用, 那么为了程序的鲁棒性, 就必须考虑信号机制给程序带来的影响:
 * 信号会打断阻塞的系统调用, 导致系统调用返回假错！
 *
 */
/*
 * 信号行为的不可靠。
 * 指的是一个信号在处理的同时, 又来了一个相同的信号, 信号处理函数现场是由内核帮忙布置的,
 * 第二次的信号处理函数的现场就把第一次的执行现场冲掉了。
 *
 * 信号的不可靠不是信号的丢失, 而是信号的行为不可靠。
 */
/*
 * 可重入函数: reentrant function。
 * 可重入函数是为了解决信号的不可靠问题。
 * 比如: 第一次调用还没有结束, 第二次调用又开始了。
 *
 * 可重入函数是第一次调用执行还没有结束, 然后又发生了第二次调用不会出错。
 * 所有的系统调用都是可重入的。所有可重入的函数不是只有系统调用, 一部分库函数也是可以重入的,
 * 例如: memcpy(); 。
 *
 */
/*
 * 信号的响应过程。
 * 内核为每个进程维护了一些内容, 例如: 一组位图(至少是两个位图: 一个位图是 mask , 另一个位图是 pending。),
 * 理论上讲: mask 和 pending 都是 32 位(bit) (8 * 4 = 32, int sig;) 的。
 *
 * mask用来表示当前的信号的状态,
 * pending用来记录当前的进程收到了哪些信号。
 *
 * mask一般情况下 32 位都是 1。
 * pending一上来所有位(32位)都是0。
 *
 * 从内核态转换为用户态继续运行某个进程的时候, 要计算(mask & pending) mask会按位与上pending。
 * 如果按位与为0, 那就说明进程没有收到任何信号。
 *
 * 信号从收到到响应有一个不可避免的延迟。思考
 * 1. 如何忽略掉一个信号的?
 * 2. 标准信号为什么要丢失?
 *
 * 假如某个时刻, 来了一个信号(SIGINTR), 那么 pending中有一位变为了1, 这个进程收到信号了,
 * 被一个中断打断之后, 进程抱着现场扎内核, 排队等待调度, 当调度到这个进程, 这个进程从kernel态
 * 转变为user态的时候, 会计算 mask&pending , 然后就会有信号的哪一位变为 1, 其它位变为0。
 * 每个信号除了有signal name还有signal number, 然后通过比较就可以得知是哪个信号来了,
 * 然后在进程中我为这个信号注册了一个行为函数即信号处理函数, 然后进程就执行这个信号处理函数。
 *
 * 某一个时刻进程收到一个信号, 进程也不知道, 进程如果没有被中断打断, 就没有办法扎内核来等待调度。
 * 调度到这个进程的时候, 这个进程从kernel态转为user态时要计算 mask&pending, 一做按位与这个
 * 进程才能发现自己收到了信号, 然后进程就去响应这个信号, 执行为这个信号注册的信号处理函数。
 *
 * 所以, 信号从收到到响应有一段不可避免的时延, 因为没有中断打断这个进程, 这个进程都不知道自己收到了信号
 * , 如果一直没有中断打断, 那就一直收不到这个信号。所以说信号是要依赖中断这个机制才能响应的。
 *
 * 然后收到这个信号(SIGINTR)后, 然后会将这个信号(SIGINTR)的mask位置为0, 把pending的
 * 这个信号的位也置为0。然后进程去执行为这个信号注册的信号处理函数。然后这个进程会再次扎入内核的
 * 等待队列, 这时会将 mask的刚刚响应的信号的mask位置为1, 而pending位现在是0, 然后再有
 * 这个信号的话, 这个pending位会再次置为1。
 *
 * 然后进程由kernel态转为user态的时候会继续 mask&pending, 看进程还没有响应哪个信号。
 *
 * 当响应完所有信号后, 这个进程kernel态转为user态继续执行。
 *
 *
 * 在进程收到多个信号的时候(多个信号的pending位为1时), 进程先响应哪个信号是没有顺序的。
 * 标准信号的响应没有严格的顺序。
 *
 *
 * SIG_IGN 是怎么做的?
 * 它就是给指定的信号的mask位置为0。然后这个信号来了, pending的这个信号位置为1, 也不行,
 * 因为 mask&pending 一与操作后, 这个信号的mask位还是0, 进程不响应。
 *
 * 标准信号为什么会丢失?
 * 因为来一万个信号, 这个信号的pending位还是置为1。
 *
 */
/*
 * 常用的信号函数:
 * kill:
// kill - send signal to a process
 给一个进程发一个信号。
int kill(pid_t pid, int sig);

// 注意: 这里的 pid参数 和 waitpid(); 中的 pid参数 类似。

DESCRIPTION
       The kill() system call can be used to send any signal to any process group or process.
       kill()系统调用可用于向任何进程组或进程发送任何信号。

       If pid is positive, then signal sig is sent to the process with the ID specified by pid.
       如果 pid 为正数，则信号 sig 将发送到具有 pid 指定的 ID 的进程。

       If pid equals 0, then sig is sent to every process in the process group of the calling process.
       如果pid等于0，则sig被发送到调用进程(即当前进程同组的所有进程)的进程组中的每个进程。
       这样相当于进行组内广播。

       If  pid  equals  -1, then sig is sent to every process for which the calling process has permission to send signals, except for process 1 (init), but
       see below.
       如果 pid 等于 -1，则将 sig 发送到调用进程(即当前进程)有权限发送信号的每个进程，进程 1 (init) 除外，但请参见下文。
       这相当于一个全局的广播信号。
       例如 init进程 发送一个全局广播给所有的子进程, 要求所有进程退出。

       If pid is less than -1, then sig is sent to every process in the process group whose ID is -pid.
       如果 pid 小于 -1，则将 sig 发送到进程组中 ID 为 -pid 的每个进程。



       If sig is 0, then no signal is sent, but existence and permission checks are still performed; this can be used  to  check  for  the  existence  of  a
       process ID or process group ID that the caller is permitted to signal.
       如果 sig 为 0，则不发送信号，但仍会执行存在和权限检查；这可用于检查允许调用者发出信号的进程 ID 或进程组 ID 是否存在。
       如果sig为0, 的话, 第一个参数pid是一个进程号或者是一个进程组号的话, 就是检测这个进程或者进程组是否存在。

       For  a  process to have permission to send a signal, it must either be privileged (under Linux: have the CAP_KILL capability in the user namespace of
       the target process), or the real or effective user ID of the sending process must equal the real or saved set-user-ID of the target process.  In  the
       case  of  SIGCONT,  it  suffices  when  the sending and receiving processes belong to the same session.  (Historically, the rules were different; see
       NOTES.)
       一个进程要有发送信号的权限，它要么是有特权的（Linux下：在目标进程的用户命名空间中具有CAP_KILL能力），
       要么发送进程的真实或有效用户ID必须等于真实或保存的目标进程的 set-user-ID。
       在 SIGCONT 的情况下，发送和接收进程属于同一个会话就足够了。 （从历史上看，规则是不同的；请参阅注释。）

RETURN VALUE
       On success (at least one signal was sent), zero is returned.  On error, -1 is returned, and errno is set appropriately.
       如果成功（至少发送了一个信号），则返回0。出错时，返回 -1，并适当设置 errno。

ERRORS
       EINVAL An invalid signal was specified.
       指定了无效信号。

       EPERM  The calling process does not have permission to send the signal to any of the target processes.
       调用进程无权将信号发送到任何目标进程。


       ESRCH  The target process or process group does not exist.  Note that an existing process might be a zombie, a process that has terminated execution,
              but has not yet been wait(2)ed for.
              目标进程或进程组不存在。请注意，现有进程可能是僵尸进程，即已终止执行但尚未等待 (2) 的进程。


 */
/*
// raise - send a signal to the caller
// 向调用者发送信号, 即自己给自己发信号。
int raise(int sig);

DESCRIPTION
       The raise() function sends a signal to the calling process or thread.  In a single-threaded program it is equivalent to

           kill(getpid(), sig);

        raise() 函数向调用进程或线程发送信号。在单线程程序中它等价于
        kill(getpid(), sig);

       In a multithreaded program it is equivalent to

           pthread_kill(pthread_self(), sig);

        在多线程程序中它等价于
        pthread_kill(pthread_self(), sig);

       If the signal causes a handler to be called, raise() will return only after the signal handler has returned.
       如果信号导致调用处理程序， raise() 将仅在信号处理程序返回后返回。

RETURN VALUE
       raise() returns 0 on success, and nonzero for failure.
       raise() 成功时返回 0，失败时返回非零。

 */
/*
alarm();函数是一个里程碑式的函数。
之前写的程序是没有时间概念的,

 // alarm - set an alarm clock for delivery of a signal.
 设置闹钟以传递信号。

unsigned int alarm(unsigned int seconds);

DESCRIPTION
       alarm() arranges for a SIGALRM signal to be delivered to the calling process in seconds seconds.
       alarm() 安排在几秒钟后将 SIGALRM 信号传递给调用进程。

       If seconds is zero, any pending alarm is canceled.
       如果秒为零，则取消任何挂起的警报。
       即如果秒为0, 就不会产生时钟信号了。

       In any event any previously set alarm() is canceled.
       在任何情况下，任何先前设置的 alarm() 都会被取消。

RETURN VALUE
       alarm()  returns  the  number  of  seconds remaining until any previously scheduled alarm was due to be delivered, or zero if there was no previously
       scheduled alarm.
       alarm() 返回到发送任何先前计划的警报之前剩余的秒数，如果没有先前计划的警报，则返回零。

 */
int main(int argc, char** argv) {
//    signal( SIGINT, SIG_IGN);
    signal( SIGINT, signalHandler );

    ssize_t writeBytes;
    for (int i = 0; i < 10; ++i) {
        do {
            writeBytes = write( 1, "*", 1 );
            if (writeBytes < 0) {
                if (errno == EINTR) {
                    continue;
                }
                perror( "write()" );
                exit( 1 );
            }
        } while (writeBytes < 0);
        sleep( 1 );
    }
    return 0;
}
